package com.example.demo;

/**
 * @Author liyueming
 * @CreateTime 2024/5/31
 * @Description
 */

import org.activiti.engine.*;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Comment;
import org.activiti.engine.task.Task;
import org.junit.Test;
import org.springframework.util.FileCopyUtils;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ActivitiTest01 {

    private static String prcess_key="myProcess_2";
    //关联的业务id
    private static String businnesskey="order_id-1";
    /*** 创建ProcessEngine流程引擎，自动创建 activiti 数据表 */
    @Test
    public void getProcessEngine() {
        // 方式一：使用activiti提供的工具类ProcessEngines，
        // 调用 getDefaultProcessEngine 会默认读取resource下的activiti.cfg.xml文件，
        // 并创建 Activiti 流程引擎 和 创建数据库表
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        System.out.println(processEngine);
        // 方式二：以编程方式创建ProcessEngineConfiguration对象
        // 1. 等同于方式一
        //ProcessEngineConfiguration configuration =ProcessEngineConfiguration.createProcessEngineConfigurationFromResourceDefault(); //ProcessEngine processEngine = configuration.buildProcessEngine();
        // 2. 自定义配置路径和文件名, 但流程引擎bean的id要等于processEngineConfiguration //ProcessEngineConfiguration configuration = ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("activiti.cfg.xml");
        // 3.自定义：配置路径、文件名和流程引擎bean的id //ProcessEngineConfiguration configuration =ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("activiti.cfg.xml", "processEngineConfiguration");
          }


    /**
     * 初始化数据库表：第一种方式
     */
    @Test
    public void testInitOne() {
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        System.err.println("processEngine = " + processEngine);
    }


    /**
     * 初始化数据库表：第二种方式
     */
    @Test
    public void testInitTwo() {
        ProcessEngineConfiguration configuration = ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("activiti.cfg.xml");
        ProcessEngine processEngine = configuration.buildProcessEngine();
        System.err.println("processEngine = " + processEngine);
    }


    /**
     * 流程部署
     */
    @Test
    public void testDeploy() {
        // 创建 ProcessEngine 对象
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 获取 repositoryService 对象
        RepositoryService repositoryService = processEngine.getRepositoryService();
        // 进行部署
        Deployment deployment = repositoryService.createDeployment()
                .addClasspathResource("process/approveProduct01.bpmn20.xml")
                .addClasspathResource("process/approveProduct01.bpmn20.png")
                .name("hh审批流程")
                .deploy();

        // 输出部署的一些信息
        System.out.println("流程部署ID：" + deployment.getId());
        System.out.println("流程部署名称：" + deployment.getName());
    }


    /**
     * 启动流程实例
     */
    @Test
    public void testStartProcess() {

        //动态设置责任人
        Map<String, Object> variables = new HashMap<>();
        variables.put("userId", "王五");
        variables.put("userIds", Arrays.asList("张三", "周鹏", "李飞"));
        // 创建 ProcessEngine 对象
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 获取 runtimeService 对象
        RuntimeService runtimeService = processEngine.getRuntimeService();
        // 根据流程定义的key启动流程实例，这个key是在定义bpmn的时候设置的
        ProcessInstance instance = runtimeService.startProcessInstanceByKey(prcess_key,businnesskey,variables);
        // 获取流程实例的相关信息
        System.out.println("流程定义的id = " + instance.getProcessDefinitionId());//myProcess_1:1:45004
        System.out.println("流程实例的id = " + instance.getId()); // 60001
        System.out.println("流程实例关联的业务id = " + instance.getBusinessKey());//order_id-1
    }


    /**
     * 查询待办任务
     */
    @Test
    public void testSelectTodoTaskList() {
        //Arrays.asList("张三", "周鹏", "李飞")
          String assignee = "王五";
         //String candidateUser = "张三";
        //String candidateUser = "周鹏";
        //String candidateUser = "李飞";
        // 创建 ProcessEngine 对象
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 获取指定用户审核任务
        TaskService taskService = processEngine.getTaskService();


        // 使用面对对象方式查询数据库
        List<Task> tasks = taskService.createTaskQuery()
                .processDefinitionKey(prcess_key)
                .taskAssignee(assignee)
                //.taskCandidateUser(candidateUser)
                .processInstanceBusinessKey(businnesskey)
                // 返回多个结果
                .list();
        // 只返回一个结果
        // .singleResult();

        // 自定义 sql 查询
        // taskService.createNativeTaskQuery();

        // 获取流程实例的相关信息
        for (Task task : tasks) {
            System.out.println("流程定义的id = " + task.getProcessDefinitionId());
            System.out.println("流程实例的id = " + task.getProcessInstanceId());
            System.out.println("任务id = " + task.getId());
            System.out.println("任务名称 = " + task.getName());
        }

         /*
        * // 查询所有的identity link   可查候选人
            L<IdentityLink> identityLinks = runtimeService.getIdentityLinksForProcessInstance(processInstanceId);

            // 遍历并打印
            for (IdentityLink identityLink : identityLinks) {
                System.out.println("Identity Link: " + identityLink.getType() + ", " + identityLink.getUserId() + ", " + identityLink.getGroupId() + ", " + identityLink.getTaskId());
            }
            * 在这个例子中，processInstanceId是你想要查询的流程实例的ID。这个方法会返回与该流程实例相关的所有身份链接。
            如果你想要查询特定类型的Identity Link，例如候选用户（candidateUsers），你可以使用TaskService：
             List<IdentityLink> candidateUsers = taskService.getCandidatesForTask(taskId);
            for (IdentityLink identityLink : candidateUsers) {
                if (identityLink.getType().equals("candidate")) {
                    System.out.println("Candidate User: " + identityLink.getUserId());
                }
            }
            * 在这个例子中，taskId是特定用户任务的ID。这个方法会返回所有被添加为候选用户的用户
        *
        * */
    }


    /**
     * 指定用户去完成任务待办：多人审批在这操作，改变审核人名称就行了
     * 并设置候选人
     * 1.activiti的流程定义节点是一个会签节点，此节点审批人有多个。
     * 2.当两个审批人同时进行complete操作的时候，会抛出并发错误。
     * 3.这是一个必现的问题。只要同一个节点的两个审批人，同时点击同意（complete）操作就会发生。
     * 这是并发操作导致的问题。因此需要加锁。主要是activiti在设计的时候，就确定了，同一个审批实例下同时只能有一个线程处理任务，如果有多个线程处理任务，则一定抛出乐观锁异常。
     */
    @Test
    public void testCompleteTask() {
        //动态设置责任人
        Map<String, Object> variables = new HashMap<>();
        // variables.put("userIds", Arrays.asList("张三", "周鹏", "李飞"));
         variables.put("flag", 2);
           String assignee = "王五";

        // String candidateUser = "张三";
      // String candidateUser = "周鹏";
        String candidateUser = "李飞";
        // 创建 ProcessEngine 对象
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 获取指定用户审核任务
        TaskService taskService = processEngine.getTaskService();
        List<Task> tasks = taskService.createTaskQuery()
                .processDefinitionKey(prcess_key)
                  // .taskAssignee(assignee)
                  .taskCandidateUser(candidateUser)
                .processInstanceBusinessKey(businnesskey)
                .list();
        if (tasks != null && !tasks.isEmpty()){
            // 当前流程图所限制，只能做审核同意的动作
            for (Task task : tasks) {
                String processInstanceId = task.getProcessInstanceId();
                String message = "审批意见：任务执行顺利，没有发现问题。";
                taskService.addComment(task.getId(), processInstanceId, message);
                taskService.complete(task.getId(),variables);
            }
        }
    }


    /**
     * 审批添加备注
     */
    @Test
    public void testAddComment() {
        String assignee = "张三";
        // 创建 ProcessEngine 对象
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 获取指定用户审核任务
        TaskService taskService = processEngine.getTaskService();
        List<Task> tasks = taskService.createTaskQuery()
                .processDefinitionKey("test")
                .taskAssignee(assignee)
                .list();
        if (tasks != null && !tasks.isEmpty()) {
            // 当前流程图所限制，只能做审核同意的动作
            for (Task task : tasks) {
                // 添加备注
                taskService.addComment(task.getId(), task.getProcessInstanceId(), assignee + "表示同意");
                taskService.complete(task.getId());
            }
        }
    }


    /**
     * 查询审批历史
     */
    @Test
    public void testSelectHistoryTask() {
        String processInstanceId = "5001";
        String assignee = "张三";
        // 创建 ProcessEngine 对象
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 查看历史信息
        HistoryService historyService = processEngine.getHistoryService();
        // 获取指定用户审核任务
        TaskService taskService = processEngine.getTaskService();
        // 获取历史审核信息
        List<HistoricActivityInstance> userTask = historyService.createHistoricActivityInstanceQuery()
                .activityType("userTask")
                // 指定实例的id
                .processInstanceId(processInstanceId)
                .taskAssignee(assignee)
                .finished()
                .list();

        for (HistoricActivityInstance instance : userTask) {
            System.out.println("任务名称 = " + instance.getActivityName());
            System.out.println("任务开始时间 = " + instance.getStartTime());
            System.out.println("任务结束时间 = " + instance.getEndTime());
            System.out.println("任务耗时 = " + instance.getDurationInMillis());
            // 获取审批批注信息
            List<Comment> taskComments = taskService.getTaskComments(instance.getTaskId());
            if (!taskComments.isEmpty()){
                System.out.println("审批批注 = " + taskComments.get(0).getFullMessage());
            }
        }
    }


    /**
     * 查询流程相关信息
     */
    @Test
    public void testDefinitionQuery() {
        // 创建 ProcessEngine 对象
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 获取仓库服务
        RepositoryService repositoryService = processEngine.getRepositoryService();
        // 获取流程定义集合
        List<ProcessDefinition> processDefinitionList = repositoryService.createProcessDefinitionQuery()
                .processDefinitionKey("test")
                // 最新的一个版本
                .latestVersion()
                .list();
        // 遍历集合
        for (ProcessDefinition definition : processDefinitionList) {
            System.out.println("流程定义id = " + definition.getId());
            System.out.println("流程定义名称 = " + definition.getName());
            System.out.println("流程定义key = " + definition.getKey());
            System.out.println("流程定义版本 = " + definition.getVersion());
            System.out.println("流程部署id = " + definition.getDeploymentId());
            System.out.println("===============");
        }
    }


    /**
     * 资源文件下载
     */
    @Test
    public void testDownloadResource() throws IOException {
        // 创建 ProcessEngine 对象
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 获取仓库服务
        RepositoryService repositoryService = processEngine.getRepositoryService();
        // 获取流程定义集合
        List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery()
                .processDefinitionKey(prcess_key)
                // 按照版本降序
                .orderByProcessDefinitionVersion()
                // 降序
                .desc()
                .list();

        // 获取最新那个
        ProcessDefinition definition = list.get(0);
        // 获取部署id
        String deploymentId = definition.getDeploymentId();

        // 获取bpmn的输入流
        InputStream bpmnInputStream = repositoryService.getResourceAsStream(deploymentId, definition.getResourceName());
        // 获取png的输入流
//        InputStream pngInputStream = repositoryService.getResourceAsStream(deploymentId, definition.getDiagramResourceName());

        String resourcePath = "C:/Users/86188/Desktop/study/" + File.separator + definition.getResourceName();
        File file = new File(resourcePath);
        if (!file.exists()) {
            file.getParentFile().mkdirs();
        }

        String diagramResourcePath = "C:/Users/86188/Desktop/study/" + File.separator + definition.getDiagramResourceName();
        file = new File(diagramResourcePath);
        if (!file.exists()) {
            file.getParentFile().mkdirs();
        }

        //复制文件
        FileCopyUtils.copy(bpmnInputStream, Files.newOutputStream(Paths.get(resourcePath)));
//        FileCopyUtils.copy(pngInputStream, Files.newOutputStream(Paths.get(diagramResourcePath)));
    }


    /**
     * 删除已经部署的流程定义
     */
    @Test
    public void testDeleteDeploy() {
        // 删除已经部署的流程定义
        String deploymentId = "75001";
        // 创建 ProcessEngine 对象
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 获取仓库服务
        RepositoryService repositoryService = processEngine.getRepositoryService();
        // 删除流程定义，如果改流程定义已有流程实例启动，则会报错
//        repositoryService.deleteDeployment(deploymentId);
        // 设置 true，级联删除流程定义，即使有启动的实例也可以删除
        repositoryService.deleteDeployment(deploymentId, true);
    }


    /**
     * 启动流程，需要进行 BusinessKey 绑定流程实例
     */
    @Test
    public void testStartBindBusinessKey() {
        String businessKey = "1";
        // 创建 ProcessEngine 对象
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        RuntimeService runtimeService = processEngine.getRuntimeService();
        // 根据流程定义的key启动流程实例，这个key是在定义bpmn的时候设置的
        // 在启动流程的时候将业务key加进去
        ProcessInstance instance = runtimeService.startProcessInstanceByKey("test", businessKey);
        // 获取流程实例的相关信息
        System.out.println("流程定义id = " + instance.getProcessDefinitionId());
        System.out.println("流程实例id = " + instance.getId());
        System.out.println("业务标识 = " + instance.getBusinessKey());
    }


    /**
     * 跑到下一个节点，需要进行审批了，此时需要获取 BusinessKey 进而获取请假单信息
     */
    @Test
    public void testGetBusinessKey() {
        // 1、获取李四的待办信息
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        TaskService taskService = processEngine.getTaskService();
        List<Task> task = taskService.createTaskQuery()
                .taskAssignee("李四")
                .processDefinitionKey("test")
                .list();

        // 2、获取 businessKey
        // 获取流程实例id
        String processInstanceId = task.get(1).getProcessInstanceId();
        RuntimeService runtimeService = processEngine.getRuntimeService();
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
                .processInstanceId(processInstanceId)
                .singleResult();
        String businessKey = processInstance.getBusinessKey();

        System.out.println("业务标识 = " + businessKey);
    }


    /**
     * 流程定义挂起与激活
     */
    @Test
    public void testSuspendAllProcessInstance() {
        // 创建 ProcessEngine 对象
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 获取仓库服务
        RepositoryService repositoryService = processEngine.getRepositoryService();
        // 获取流程定义对象
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .processDefinitionKey(prcess_key)
                .singleResult();
        boolean suspended = processDefinition.isSuspended();
        // 输出流程定义状态
        System.out.println("流程定义状态：" + (suspended ? "已挂起" : "已激活"));
        String processDefinitionId = processDefinition.getId();

        if (suspended) {
            repositoryService.activateProcessDefinitionById(processDefinitionId, true, null);
            System.out.println("流程id：" + processDefinitionId + "已激活");
        } else {
            repositoryService.suspendProcessDefinitionById(processDefinitionId, true, null);
            System.out.println("流程id：" + processDefinitionId + "已挂起");
        }
    }


    /**
     * 流程实例挂起与激活
     */
    @Test
    public void testProcessInstance() {
        // 创建 ProcessEngine 对象
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        RuntimeService runtimeService = processEngine.getRuntimeService();
        // 获取流程定义对象
        List<ProcessInstance> processInstanceList = runtimeService.createProcessInstanceQuery()
                .processDefinitionKey("test")
                .list();

        // 遍历集合
        for (ProcessInstance processInstance  : processInstanceList) {
            boolean suspended = processInstance.isSuspended();
            // 输出流程定义状态
            System.out.println("流程实例状态：" + processInstance + "," + (suspended ? "已挂起" : "已激活"));
            String processDefinitionId = processInstance.getId();

            if (suspended) {
                runtimeService.activateProcessInstanceById(processDefinitionId);
                System.out.println("流程实例id：" + processDefinitionId + "已激活");
            } else {
                runtimeService.suspendProcessInstanceById(processDefinitionId);
                System.out.println("流程实例id：" + processDefinitionId + "已挂起");
            }
        }
    }


}
